# Generate CSS overrides template which applies the marketplace custom colors and
# logos on top of the default application.css.
#
# Algorythm:
# 1. Compile default application styles
# 2. Compile the stylesheet again with custom colors and images set to known values
# 3. Compute the actual CSS selectors affected by the customizations
# 4. Compute CSS color values for all color variations (darken, lighten) that are used in our .scss styles
# 5. Lookup all color values and replace colors and image URLs with HAML-style template strings
# 6. Generate a HAML template file, intended to be rendered on each page

namespace :sharetribe do
  CUSTOM_COLOR1 = "ff0000"
  CUSTOM_COLOR2 = "00ff00"
  CUSTOM_COLOR3 = "0000ff"
  CUSTOM_COLOR4 = "00ffff"

  SOURCE_DIR    = "app/assets/stylesheets"
  SOURCE_FILE   = "application.scss"
  VARIABLE_FILE = "mixins/default-colors.scss"

  def default_variable_hash
    {
      "link" => "#000000",
      "link2" => "#000000",
      "slogancolor" => "#000000",
      "descriptioncolor" => "#000000",
      "cover-photo-url" => "\"default-cover-photo\"",
      "small-cover-photo-url" => "\"default-small-cover-photo\"",
      "wide-logo-lowres-url" => "\"default-wide-logo-lowres\"",
      "wide-logo-highres-url" => "\"default-wide-logo-highres\"",
      "square-logo-lowres-url" => "\"default-square-logo-lowres\"",
      "square-logo-highres-url" => "\"default-square-logo-highres\""
    }
  end

  def custom_variable_hash
    {
      "link" => "##{CUSTOM_COLOR1}",
      "link2" => "##{CUSTOM_COLOR2}",
      "slogancolor" => "##{CUSTOM_COLOR3}",
      "descriptioncolor" => "##{CUSTOM_COLOR4}",
      "cover-photo-url" => "\"cover-photo\"",
      "small-cover-photo-url" => "\"small-cover-photo\"",
      "wide-logo-lowres-url" => "\"wide-logo-lowres\"",
      "wide-logo-highres-url" => "\"wide-logo-highres\"",
      "square-logo-lowres-url" => "\"square-logo-lowres\"",
      "square-logo-highres-url" => "\"square-logo-highres\""
    }
  end

  def compile(vars, target)
    StylesheetCompiler.compile(SOURCE_DIR, SOURCE_FILE, target, VARIABLE_FILE, vars)
  end

  def find_color(color, color_map)
    color_map.each do |color_name, variations|
      variations.each do |fn, levels|
        return { color: color_name, fn: fn, level: levels[color] } if levels[color]
      end
    end
    StandardError.new("Color #{color} not found in color map!")
  end

  def color_template_string(color_spec)
    "#\#{ ColorUtils.#{color_spec[:fn]}(#{color_spec[:color]}, #{color_spec[:level]}) }"
  end

  def url_template_string(url)
    "\#{ image_map[:#{url.gsub(/^.*\//, "").tr('-', '_')}] }"
  end

  # Replace color values and image urls with template expansion
  def replace_with_template_strings(css_string, color_search_map)
    css_string.lines.map do |o|
      o.rstrip.gsub(/(color|background): (#[\w]{6}|[a-z]+);/) { |match|
        "#{Regexp.last_match[1]}: #{color_template_string(find_color(Regexp.last_match[2], color_search_map))};"
      }.gsub(/url\("([^"]+)"\);/) { |match|
        "url(\"#{url_template_string(Regexp.last_match[1])}\");"
      }
    end.join("\n")
  end

  def write_template(file, string)
    head = <<-HERE.strip_heredoc
      - # This file is automatically generated by "rake sharetribe:cs_extract" task.
      - # Run "rake sharetribe:cs_extract" to update this template after modifying any styles.
    HERE

    File.open(file, "w") do |f|
      f.write(head)

      f.write(":css\n")
      string.lines.each do |l|
        f.write("  #{l.strip}\n")
      end
    end
  end

  def transforms_hash(color)
    {
      color: color,
      transforms: [
        { fn: :darken, levels: (0..100) },
        { fn: :lighten, levels: (0..100) }
      ]
    }
  end

  def search_map(color_map, color)
    {
      darken: color_map[color]["darken"].invert,
      lighten: color_map[color]["lighten"].invert
    }
  end

  def override_rule_set!(a, b)
    a.each_declaration do |prop|
      if b.get_value(prop) == a.get_value(prop)
        b.remove_declaration!(prop)
      end
    end
    b
  end

  # Own implementation of to_s which doesn't print each selector separately
  # as CssParser::Parser.to_s does
  def css_parser_to_s(css_parser)
    out = ""
    css_parser.rules_by_media_query.each do |media_type, rule_sets|
      media_block = (media_type != :all)
      out << "@media #{media_type} {\n" if media_block

      rule_sets.each do |rule_set|
        indent = media_block ? "  " : ""
        out << "#{indent}#{rule_set}\n"
      end

      out << "}\n" if media_block
    end
    out
  end

  def optimize_rule_sets(rule_sets)
    dedup_map = {}
    rule_sets.each do |rule_set|
      declarations = rule_set.declarations_to_s
      if dedup_map.key?(declarations)
        dedup_map[declarations].selectors.concat(rule_set.selectors)
      else
        dedup_map[declarations] = rule_set
      end
    end
    dedup_map.values
  end

  # Find selectors with same style declarations and aggregate them
  def optimize_css_parser(css_parser)
    result = CssParser::Parser.new
    css_parser.rules_by_media_query.each do |media_type, rule_sets|
      optimize_rule_sets(rule_sets).each do |rule_set|
        result.add_rule_set!(rule_set, media_type)
      end
    end
    result
  end

  def find_overrides(default_styles, custom_styles)
    default_css = CssParser::Parser.new
    custom_css = CssParser::Parser.new
    overrides = CssParser::Parser.new

    puts "Parsing default styles...".green
    default_css.load_string!(default_styles)

    puts "Parsing custom styles...".green
    custom_css.load_string!(custom_styles)

    default_rulesets = default_css.rules_by_media_query()
    custom_rulesets = custom_css.rules_by_media_query()

    puts "Finding overrides...".green
    default_rulesets.each do |media_type, rule_sets|
      rule_sets.zip(custom_rulesets[media_type]).each do |default, custom|
        if default.to_s != custom.to_s
          overrides.add_rule_set!(override_rule_set!(default, custom), media_type)
        end
      end
    end

    css_parser_to_s(optimize_css_parser(overrides))
  end

  task :cs_extract, [:overrides] => :environment do |t, args|
    overrides_file = args.overrides || "app/views/layouts/_style_overrides.haml"
    defaults_file = "tmp/default.css"
    custom_file = "tmp/custom.css"

    puts "Compiling styles with default variables...".green
    compile(default_variable_hash, defaults_file)

    puts "Compiling styles with custom variables...".green
    compile(custom_variable_hash, custom_file)

    default_styles = File.read(defaults_file)
    custom_styles = File.read(custom_file)

    overrides = find_overrides(default_styles, custom_styles)

    color_map = ColorUtils.sass_color_variations(
      [
        transforms_hash(CUSTOM_COLOR1),
        transforms_hash(CUSTOM_COLOR2),
        transforms_hash(CUSTOM_COLOR3),
        transforms_hash(CUSTOM_COLOR4)
      ]
    )

    color_search_map = {
      color1: search_map(color_map, CUSTOM_COLOR1),
      color2: search_map(color_map, CUSTOM_COLOR2),
      slogancolor: search_map(color_map, CUSTOM_COLOR3),
      descriptioncolor: search_map(color_map, CUSTOM_COLOR4)
    }

    write_template(overrides_file, replace_with_template_strings(overrides, color_search_map))

    puts "Style overrides template written to #{overrides_file}!".green
  end
end
